//
//  ckr_spec_cpp.mm
//  ckrSpec
//
//  Created by Keith Ray on 2006.10.16.
//  Copyright 2006 __MyCompanyName__. All rights reserved.
//

#include "ckr_spec_cpp.h"
#include "ckr_spec_StringUtils.h"
#include <iostream>
#include <math.h>

using namespace std;

namespace  ckr
{

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Context::Context( const std::string& nameIn,
						int						lineIn, 
						const char*				fileNameIn )
	: myName( nameIn )
	, myHasBeenRun( false )
	, myHasFailed( false )
	, myListener( NULL )
	, myContextLine( lineIn )
	, myContextFileName( fileNameIn )
{
	resetTimes();
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Context::~Context()
{
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

std::string Context::name() const
{
	return myName;
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

static const double kMillion = 1000000.0;

double Context::duration() const
{
	// in seconds
	double result = myStopTime.tv_sec * kMillion + myStopTime.tv_usec;
	result -= myStartTime.tv_sec * kMillion + myStartTime.tv_usec;
	result /= kMillion;
	return result;
}

void Context::setDuration( double seconds )
{
	myStopTime.tv_sec = (time_t) trunc( seconds );
	myStopTime.tv_usec = (suseconds_t)( (seconds - myStopTime.tv_sec)
		* kMillion );
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

bool Context::hasBeenRun() const
{
	return myHasBeenRun;
}

void Context::setHasBeenRun( bool valIn )
{
	myHasBeenRun = valIn;
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

bool Context::hasFailed() const
{
	return myHasFailed;
}

void Context::setHasFailed( bool valIn )
{
	myHasFailed = valIn;
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

void Context::setup()
{
	// a subclass should implement, maybe
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

void Context::spec()
{
	// a subclass should implement
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

void Context::teardown()
{
	// a subclass should implement, maybe
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

void Context::resetTimes()
{
	myStartTime.tv_sec = 0;
	myStartTime.tv_usec = 0;  // millionths of seconds

	myStopTime.tv_sec = 0;
	myStopTime.tv_usec = 0;  // millionths of seconds
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

void Context::startTiming()
{
	struct timezone UTCtimezone = { 0, 0 };
	::gettimeofday( & myStartTime, & UTCtimezone );
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

void Context::stopTiming()
{
	struct timezone UTCtimezone = { 0, 0 };
	::gettimeofday( & myStopTime, & UTCtimezone );
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

void Context::run_spec( SpecListener* listenerIn )
{
	myListener = listenerIn;

	startTiming();

	try
	{
		setup();
	}
	catch( std::exception& ex )
	{
		this->report_exception( ex, "setup" );
		return;
	}

	try
	{
		spec();
	}
	catch( std::exception& ex )
	{
		this->report_exception( ex, "spec" );
	}

	try
	{
		teardown();
	}
	catch( std::exception& ex )
	{
		this->report_exception( ex, "teardown" );
	}

	stopTiming();
	myHasBeenRun = true;

	myListener = NULL;
	// myListener is a "temporary field", which is a code-smell, but
	// some of the alternatives to avoid that smell are worse (methods with multiple
	// arguments where we want only one argument.) So I'm adding a comment here
	// to "fix" that smell. (And yes "Comments" is a code smell too.)
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

// refactored three outStream blocks into report_exception() to fix
// code smell "duplicated logic". Also it violated Single-Reponsibility-
// Principle.

void Context::report_exception( const std::exception& ex, 
						const std::string& sectionName )
{
	assert( myListener != NULL );
	myHasFailed = true;

	std::stringstream strm;
	strm << "Exception thrown in "
		<< sectionName
		<< " of '"
		<< this->myName 
		<< "', ex.what = '"
		<< ex.what()
		<< "'";

	myListener->spec_failed( myName, myContextLine, myContextFileName, strm.str() );
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

void Context::fail_impl( const std::string& failMessageIn,
									int				lineNumberIn,
									const char*		srcFileNameIn )
{
	myHasFailed = true;
	
	assert( myListener != NULL );
	myListener->spec_failed( this->myName, lineNumberIn, srcFileNameIn, 
		failMessageIn );
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

void Context::fail_not_throw_impl( const std::exception& ex,
									int				lineNumberIn,
									const char*		srcFileNameIn )
{
	myHasFailed = true;
	assert( myListener != NULL );

	std::stringstream strm;
	strm << "should not throw '" << ex.what() << "'";

	myListener->spec_failed( myName, lineNumberIn, srcFileNameIn,
			strm.str() );
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

void Context::report_failure_operation_cstring( 
				const char*		value, 
				const char*		valueName,
				const char*		other, 
				const char*		otherName, 
				const char*		operation_name,
				int				lineNumberIn,
				const char*		srcFileNameIn )
{
	myHasFailed = true;

	assert( myListener != NULL );
	std::stringstream strm;

	writeNameAndStringValue( strm, value, valueName );

	strm << " "
		<< operation_name
		<< " ";

	writeNameAndStringValue( strm, other, otherName );

	myListener->spec_failed( myName, lineNumberIn, srcFileNameIn,
		strm.str() );
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

void Context::report_failure_bool( 
				bool     		value, 
				const char*		valueName,
				const char*		operation_name,
				int				lineNumberIn,
				const char*		srcFileNameIn )
{
	assert( myListener != NULL );

	myHasFailed = true;
	std::stringstream strm;
	writeNameAndBoolValue( strm, value, valueName );
	strm << " " << operation_name;
	
	myListener->spec_failed( myName, lineNumberIn, srcFileNameIn,
			strm.str() );
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

void Context::writeNameAndStringValue( 
			std::ostream& strm,
			const char* value, 
			const char * valueName )
{
	if ( value == NULL )
	{
		strm << valueName << " (" 	<< "(NULL POINTER)" << ")";
	}
	else
	{
		strm << valueName << " (" << value << ")";
	}
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

void Context::writeNameAndBoolValue( 
			std::ostream&	strm,
			bool				value, 
			const char *	valueName )
{
	assert( myListener != NULL );

	strm << valueName 
		<< " (" << ( value ? "true" : "false" ) 
		<< ")";
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

void Context::report_failure_between_cstring( 
			const char*		value, 
			const char*		valueName,
			const char* 	lowValueIn,
			const char*		lowValueNameIn, 
			const char*		highValueIn,
			const char*		highValueNameIn, 
			int				lineNumberIn,
			const char*		srcFileNameIn )
{
	myHasFailed = true;

	assert( myListener != NULL );
	std::stringstream strm;

	writeNameAndStringValue( strm, value, valueName );
	strm << " should be between ";
	writeNameAndStringValue( strm, lowValueIn, lowValueNameIn );
	strm << " and ";
	writeNameAndStringValue( strm, highValueIn, highValueNameIn );

	myListener->spec_failed( myName, lineNumberIn, srcFileNameIn,
		strm.str() );
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

void Context::report_failure_not_between_cstring( 
			const char*		value, 
			const char*		valueName,
			const char*		lowValueIn,
			const char*		lowValueNameIn, 
			const char*		highValueIn,
			const char* 	highValueNameIn, 
			int				lineNumberIn,
			const char*		srcFileNameIn )
{
	myHasFailed = true;

	assert( myListener != NULL );
	std::stringstream strm;

	writeNameAndStringValue( strm, value, valueName );
	strm << " should not be between ";
	writeNameAndStringValue( strm, lowValueIn, lowValueNameIn );
	strm << " and ";
	writeNameAndStringValue( strm, highValueIn, highValueNameIn );

	myListener->spec_failed( myName, lineNumberIn, srcFileNameIn,
		strm.str() );
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

void Context::report_failure_within_cstring( 
			const char*		value, 
			const char*		valueName,
			const char*		lowValueIn,
			const char*		lowValueNameIn, 
			const char*		highValueIn,
			const char*		highValueNameIn, 
			int				lineNumberIn,
			const char*		srcFileNameIn )
{
	myHasFailed = true;

	assert( myListener != NULL );
	std::stringstream strm;

	writeNameAndStringValue( strm, value, valueName );
	strm << " should be within ";
	writeNameAndStringValue( strm, lowValueIn, lowValueNameIn );
	strm << " through ";
	writeNameAndStringValue( strm, highValueIn, highValueNameIn );

	myListener->spec_failed( myName, lineNumberIn, srcFileNameIn,
		strm.str() );
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

void Context::report_failure_not_within_cstring( 
			const char*		value, 
			const char*		valueName,
			const char*		lowValueIn,
			const char*		lowValueNameIn, 
			const char*		highValueIn,
			const char*		highValueNameIn, 
			int				lineNumberIn,
			const char*		srcFileNameIn )
{
	myHasFailed = true;

	assert( myListener != NULL );
	std::stringstream strm;

	writeNameAndStringValue( strm, value, valueName );
	strm << " should not be within ";
	writeNameAndStringValue( strm, lowValueIn, lowValueNameIn );
	strm << " through ";
	writeNameAndStringValue( strm, highValueIn, highValueNameIn );

	myListener->spec_failed( myName, lineNumberIn, srcFileNameIn,
		strm.str() );
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

void Context::report_failure_not_null( 
			const void*		value, 
			const char*		valueName,
			int				lineNumberIn,
			const char*		srcFileNameIn )
{
	myHasFailed = true;

	assert( myListener != NULL );

	std::stringstream strm;
	strm << valueName << " (" << value << ")";
	strm << " should be null";

	myListener->spec_failed( myName, lineNumberIn, 
		srcFileNameIn, strm.str() );
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

void Context::report_failure_null( 
			const void*		value, 
			const char*		valueName,
			int				lineNumberIn,
			const char*		srcFileNameIn )
{
	myHasFailed = true;

	assert( myListener != NULL );

	std::stringstream strm;
	strm << valueName << " (" << value << ")";
	strm << " should not be null";

	myListener->spec_failed( myName, lineNumberIn, 
		srcFileNameIn, strm.str() );
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

bool ContextNameLessThan( const Context* lhs, const Context* rhs )
{
	return toUpper(lhs->name()) < toUpper(rhs->name());
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

bool ContextDurationLessThan( const Context* lhs, const Context* rhs )
{
	return lhs->duration() < rhs->duration();
}
//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

bool ContextHasBeenRunLessThan( const Context* lhs, const Context* rhs )
{
	bool result = lhs->hasBeenRun() < rhs->hasBeenRun();
	if ( not result )
	{
		result = lhs->duration() < rhs->duration();
	}
	return result;
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

bool ContextFailingLessThan( const Context* lhs, const Context* rhs )
{
	return lhs->hasFailed() > rhs->hasFailed();
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

}; // end namespace

